package org.hubwork.base.common;

import java.util.HashMap;
import java.util.concurrent.CountDownLatch;

import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;

@Scope("singleton")
public class DynamicBeanFactory extends ClassPathXmlApplicationContext
{
	private static final long THREAD_ID_NONE = -1L;
	private boolean autoRegister = true;
	private volatile Long localthread = THREAD_ID_NONE;
	@SuppressWarnings("rawtypes")
	static HashMap<String, Class> prototypes = new HashMap<String, Class> ();
	@SuppressWarnings("rawtypes")
	static HashMap<String, Class> requests = new HashMap<String, Class> ();

	private boolean reg_prototypes = true;
	private DefaultListableBeanFactory appManagedBeans;

	private CountDownLatch countDonwLatch = null;
	private Integer counter = new Integer (0);
	private boolean isDebugEnabled = false;


	DynamicBeanFactory (String[] beanDefinitionFiles)
	{
		super (beanDefinitionFiles);

		this.reset ();
		try
		{
			org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor tasa = (org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor) this.appManagedBeans.getBean ("org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor");
			TransactionInterceptor ti = (TransactionInterceptor) tasa.getAdvice ();
			ti.setTransactionAttributeSource ((TransactionAttributeSource) this.appManagedBeans.getBean ("transactionAttributeSource"));
		}
		catch (Throwable t)
		{
		}
	}

	public synchronized void reset ()
	{
		this.appManagedBeans = new DefaultListableBeanFactory (this.getBeanFactory ());
		this.appManagedBeans.copyConfigurationFrom (this.getBeanFactory ());

		AopConfigUtils.registerAutoProxyCreatorIfNecessary(this.appManagedBeans);
		AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(this.appManagedBeans);

		for (String scope : this.getBeanFactory ().getRegisteredScopeNames ())
		{
			this.appManagedBeans.registerScope (scope, this.getBeanFactory ().getRegisteredScope (scope));
		}
	}

	@SuppressWarnings({ "deprecation", "rawtypes" })
	void registerSingleton (String name, Class clazz, MutablePropertyValues props) throws BeansException
	{
		RootBeanDefinition def = new RootBeanDefinition (clazz, props);
		def.setLazyInit (false);
		this.appManagedBeans.registerBeanDefinition (name, def);
	}

	@SuppressWarnings("rawtypes")
	void registerSingleton (String name, Class clazz) throws BeansException
	{
		RootBeanDefinition def = new RootBeanDefinition (clazz);
		def.setLazyInit (false);
		this.appManagedBeans.registerBeanDefinition (name, def);
	}
	@SuppressWarnings({ "rawtypes", "deprecation" })
	void registerPrototype (String name, Class clazz, MutablePropertyValues props) throws BeansException
	{
		RootBeanDefinition def = new RootBeanDefinition (clazz, props);
		def.setScope (BeanDefinition.SCOPE_PROTOTYPE);
		def.setLazyInit (false);
		this.appManagedBeans.registerBeanDefinition (name, def);
	}

	@SuppressWarnings("rawtypes")
	void registerPrototype (String name, Class clazz) throws BeansException
	{
		RootBeanDefinition def = new RootBeanDefinition (clazz);
		def.setScope (BeanDefinition.SCOPE_PROTOTYPE);
		def.setLazyInit (false);
		this.appManagedBeans.registerBeanDefinition (name, def);
	}

	@SuppressWarnings ({ "unchecked", "static-access" })
	<T, U extends T> T getBean (String name, Class<T> clazz, Class<U> defImpl) throws BeansException
	{
		T out = null;
		try
		{
			await ();
			out = (T) this.appManagedBeans.getBean (name, clazz);
		}
		catch (NoSuchBeanDefinitionException ex)
		{
			synchronized (localthread)
			{
				getLock(name);
			}
			if (!threadHasLock ())
				return getBean (name, clazz, defImpl);

			if (clazz == null && defImpl == null)
			{
				freeLock ();
				throw ex;
			}
			try
			{
				if (out == null && this.autoRegister)
				{
					String scope = ScopeType.SINGLETON.toString();
					Class<U> inst = defImpl;

					if (inst != null && inst.getAnnotation (Scope.class) != null)
					{
						scope = defImpl.getAnnotation (Scope.class).value ();
					}
					else if (inst == null && defImpl != null && defImpl.getAnnotation (Scope.class) != null)
					{
						scope = defImpl.getAnnotation (Scope.class).value ();
					}
					else if (inst == null && defImpl == null && clazz.getAnnotation (Scope.class) != null)
					{
						scope = clazz.getAnnotation (Scope.class).value ();
					}
					if(scope == ScopeType.PROTOTYPE.toString()){
						if (reg_prototypes)
						{
							if (inst == null)
							{
								if (isDebugEnabled)
								{
									printDebugInfo (name, clazz, scope);
								}
								this.registerPrototype (name, clazz);
							}
							else
							{
								if (isDebugEnabled)
								{
									printDebugInfo (name, inst, scope);
								}
								this.registerPrototype (name, inst);
							}
						}
						else
						{
							try
							{
								if (inst != null)
								{
									this.prototypes.put (name, inst);
									return (T) BeanUtils.instantiateClass (inst);
								}
								else
								{
									this.prototypes.put (name, clazz);
									return (T) BeanUtils.instantiateClass (clazz);
								}
							}
							catch (Exception ex2)
							{
								throw new RuntimeException (ex2);
							}
						}
					}else if(scope == ScopeType.SINGLETON.toString()){
						if (inst != null)
						{
							if (isDebugEnabled)
							{
								printDebugInfo (name, inst, scope);
							}
							this.registerSingleton (name, inst);
						}
						else if (defImpl == null)
						{
							if (isDebugEnabled)
							{
								printDebugInfo (name, clazz, scope);
							}
							this.registerSingleton (name, clazz);
						}
						else
						{
							if (isDebugEnabled)
							{
								printDebugInfo (name, defImpl, scope);
							}
							this.registerSingleton (name, defImpl);
						}
					}else if (scope == ScopeType.REQUEST.toString()){
						if (inst != null)
						{
							defImpl = (Class<U>) inst;
						}
						if (defImpl == null)
						{
							defImpl = (Class<U>) clazz;
						}
						this.requests.put (name, defImpl);
						return ThreadContext.get (name, defImpl);
					}else{
						//do nothing
					}
					out = (T) this.appManagedBeans.getBean (name, clazz);
				}
				else
				{
					throw ex;
				}
			}
			finally
			{
				freeLock ();
			}
		}
		return out;
	}

	public <U> void printDebugInfo (String name, Class<U> inst, String scope)
	{
		/*if (!LogUtil.class.isAssignableFrom (inst))
		{
			LogUtil.debug (DynamicBeanFactory.class, "printDebugInfo", "Autoregistering bean: %s (%s)", name, scope);
		}*/
	}

	public boolean threadHasLock ()
	{
		return localthread.compareTo (Thread.currentThread ().getId ()) == 0;
	}

	public void await ()	{
		try		{
			if (!threadHasLock () && localthread != THREAD_ID_NONE)			{
				countDonwLatch.await ();
			}
		}		catch (InterruptedException e)		{
			//throw new ApiRuntimeException (new MessageData (MessageKeyGeneral.UNEXPECTED), e);
		}
	}

	public void freeLock ()
	{
		if (threadHasLock ())
		{
			counter--;
			if (counter == 0)
			{
				localthread = THREAD_ID_NONE;
				countDonwLatch.countDown ();
				/*if (isDebugEnabled)
				{
					LogUtil.debug (DynamicBeanFactory.class, "freeLock", "Freeing Bean Initialization thread[%d] ", Thread.currentThread ().getId ());
				}*/
			}
		}
	}
	public  void getLock(Object name){
		//lock only if no other thread has the lock
		if (localthread == THREAD_ID_NONE){
			counter = 1;
			countDonwLatch = new CountDownLatch (1);
			localthread = Thread.currentThread ().getId ();
			/*if (isDebugEnabled){
				LogUtil.debug (DynamicBeanFactory.class, "getLock", "Locking Bean Initialization thread[%d] for Bean: %s ",Thread.currentThread ().getId (), name);
			}*/
		} else if (threadHasLock ()){
			counter++;
		}
	}
}
